/*
 *  YATUN  - Yet another tunneling application
 *
 *  Michal Demin - 2007
 *
 *  SchedQueue.cpp
 *  Implementacia scheduleru packetov, pre spolahlive dorucovanie
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "SchedQueue.h"

#undef DEBUG

SchedQueue::SchedQueue ()
{ 
  Queue = NULL;
// resetnem pocitadla
  SeqNum = 0;
  Queued = 0;
  MaxQueued = 100;
  Resent = 0;
  Full = 0;
// nastavim defaultne timeouty
  DynamicTimeout = 0;
  DynamicMult = 6;

  RetryTimeout.tv_sec = 0;
  RetryTimeout.tv_usec = 500000;

  MAXTimeout.tv_sec = 0;
  MAXTimeout.tv_usec = 175000;

  MINTimeout.tv_sec = 0;
  MINTimeout.tv_usec = 20000;

  RTATime.tv_sec = 0;
  RTATime.tv_usec = 100000;
}

SchedQueue::~SchedQueue ()
{
  _eQueue *tmp;
//Zmazem Queue, kedze koncime
  while (Queue) {
    delete Queue->Pack;

    tmp = Queue->Next;
    delete Queue;

    Queue = tmp;
  }
}

/*
 * Posle packet a prida ho do fronty
 */
int
SchedQueue::AddSend (Packet * inPack)
{
  _eQueue *qtmp;
  struct timeval time;

  // vytvorim si novy prvok v queue
  qtmp = new _eQueue;
  qtmp->Next = NULL;
  inPack->SeqNum = SeqNum;	// priradim dalsie cislo packetu
  SeqNum++;
  qtmp->Pack = new Packet (*inPack);

  gettimeofday (&time, NULL);	// oznackujem packet 
  qtmp->Timestamp.tv_sec = time.tv_sec;
  qtmp->Timestamp.tv_usec = time.tv_usec;

  if (Queue) {
    QueueLast->Next = qtmp;	// a pridam do fronty
    QueueLast = QueueLast->Next;
  } else {
    Queue = QueueLast = qtmp;
  }
  
  // poslem
  IO->Send (qtmp->Pack);

  Queued++; 
  if(!Full && (Queued>MaxQueued)) {
    Full=1;
    fprintf(stderr,"Queue is FULL \n");
  }

#ifdef DEBUG
  fprintf (stderr, "Sending packet seq = %d \n", qtmp->Pack->SeqNum);
#endif

  return qtmp->Pack->SeqNum;
}

/*
 *  Skontrolujem, ze z ktorych packetov este nedosiel ACK
 */
int
SchedQueue::Check ()
{
  _eQueue *qtmp = Queue;
  _eQueue *tmp;
  struct timeval curTime;

  gettimeofday (&curTime, NULL);
  timersub (&curTime, &RetryTimeout, &curTime); // odpocitam cas timeoutu

  tmp = Queue;

  while (qtmp) {
    if (!timerisset (&qtmp->Timestamp)) {	// ked nie je timestamp tak ide prec
      if (qtmp == Queue) {
	Queue = Queue->Next;
	delete qtmp->Pack;
	delete qtmp;

	qtmp = Queue;
      } else {
	tmp->Next = qtmp->Next;
	delete qtmp->Pack;
	delete qtmp;

	qtmp = tmp->Next;
      }
      Queued--;
      if(Full && (Queued<MaxQueued/2)) {
        Full=0;
	fprintf(stderr,"Queue is ready for action!\n");
      }
    }
    if (qtmp == NULL) { 
      break;
    }
    // skontrolujem timeout, ak pretiekol, tak odoslem packet znova a resetnem timeout
    if (timerisset(&qtmp->Timestamp) && timercmp (&qtmp->Timestamp, &curTime, <)) {
      Resent++;
      fprintf (stderr, "Resending packet Seq = %d  Resent = %ld\n", qtmp->Pack->SeqNum,
	       Resent);
      IO->Send (qtmp->Pack);
      gettimeofday (&qtmp->Timestamp, NULL);	// nastavym novy stamp a pokracujem na dalsom packete
    }

    tmp = qtmp;
    qtmp = qtmp->Next;

  }

  return 0;
}

/*
 * Vratil sa mi ACK, tak musim nastavit prislusny packet na zmazanie
 */
int
SchedQueue::Remove (int Seq)
{
  _eQueue *qtmp = Queue;

  while (qtmp) {
    if (qtmp->Pack->SeqNum == Seq) {
      ComputeRTA (qtmp->Timestamp); // vypocitam RTA  (round trip avg)
      timerclear (&qtmp->Timestamp);
      return 0;
    }
    qtmp = qtmp->Next;
  }

  return 1;
}

void
SchedQueue::ComputeRTA (struct timeval &inTime)
{
  struct timeval curTime;

  gettimeofday (&curTime, NULL);
  timersub (&curTime, &inTime, &inTime);	// vypocitam si kolko trva cesta tam aj naspet
#ifdef DEBUG
  fprintf (stderr, "Time = %u.%06u sec \n", (unsigned) inTime.tv_sec,
	   (unsigned) inTime.tv_usec);
  fprintf (stderr, "RTATime = %u.%06u sec ", (unsigned) inTime.tv_sec,
	   (unsigned) inTime.tv_usec);
#endif

  // AKA "low pass" filter, aby RTA moc velmi neskakalo
  RTATime.tv_usec = (unsigned) (0.80 * RTATime.tv_usec + 0.20 * inTime.tv_usec); 
  RTATime.tv_sec = (unsigned) (0.80 * RTATime.tv_sec + 0.20 * inTime.tv_sec);

  if (RTATime.tv_usec > 1000000) { // dopocitam sekundy
    RTATime.tv_usec -= 1000000;
    RTATime.tv_sec += 1;
  }
#ifdef DEBUG
  fprintf (stderr, "new RTATime = %u.%06u sec \n", (unsigned) RTATime.tv_sec,
	   (unsigned) RTATime.tv_usec);
#endif

  RetryTimeout.tv_usec = RTATime.tv_usec * DynamicMult;
  RetryTimeout.tv_sec = RTATime.tv_sec * DynamicMult;

  if (RetryTimeout.tv_usec > 1000000) { // dopocitam sekundy
    RetryTimeout.tv_sec += RetryTimeout.tv_usec / 1000000;
    RetryTimeout.tv_usec = RetryTimeout.tv_usec % 1000000;
  }
  
 // ak sme nahodou za extremom tak upravim
  if (timercmp (&RetryTimeout, &MAXTimeout, >)) {
    RetryTimeout = MAXTimeout;
  } else if (timercmp (&RetryTimeout, &MINTimeout, <)) {
    RetryTimeout = MINTimeout;
  }
#ifdef DEBUG
  fprintf (stderr, "new RetryTimeout = %u.%06u sec \n", (unsigned) RetryTimeout.tv_sec,
	   (unsigned) RetryTimeout.tv_usec);
#endif

}

/*
 * zmazeme frontu
 */
void SchedQueue::FlushQueue ()
{
  _eQueue *tmp;

  while (Queue) { // zmazem frontu
    delete Queue->Pack;

    tmp = Queue->Next;
    delete Queue;

    Queue = tmp;
  }
  Full = 0;
}

int
SchedQueue::IsFull() {
  return Full;
}

void
SchedQueue::SetIO (c_Plug_IO * tmp)
{
  IO = tmp;
}

void
SchedQueue::SetResendTimeout (struct timeval &inTime)
{
  RetryTimeout = inTime;
}

void
SchedQueue::SetResendMAXTimeout (struct timeval &inTime)
{
  MAXTimeout = inTime;
}

void
SchedQueue::SetResendMINTimeout (struct timeval &inTime)
{
  MINTimeout = inTime;
}

void
SchedQueue::SetDynamicTimeout (int inVal)
{
  DynamicTimeout = inVal;
}

void
SchedQueue::SetDynamicMul (int inVal)
{
  DynamicMult = inVal;
}

void 
SchedQueue::SetMaxQueued (int inVal)
{
  MaxQueued = inVal;
}

void
SchedQueue::SetConf(struct SchedQueueConf &in)
{
    MaxQueued = in.MaxQueued;
    DynamicMult = in.DynamicMult;
    DynamicTimeout = in.DynamicTimeout;
    
    MINTimeout = in.MINTimeout;
    MAXTimeout = in.MAXTimeout;
    RetryTimeout = in.RetryTimeout;
}
